package com.atguigu.tingshu.account.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

	@Autowired
	private UserAccountMapper userAccountMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AccountLockResultVo checkAndLockAccount(AccountLockVo accountLockVo) {
        //1.初始化返回结果
        AccountLockResultVo accountLockResultVo = new AccountLockResultVo();

        // OpenFeign重试机制/超时机制
        // OpenFeign重试（默认不重试：自带了重试机制【重试5次】 自定义重试逻辑）
        // 假设：开启了openFeign的重试机制（幂等性保证：openfeign保证幂等性）
        // 10000s
        // 1.获取属性值
        BigDecimal amount = accountLockVo.getAmount();// 锁定金额
        Long userId = accountLockVo.getUserId();// 用户id
        String content = accountLockVo.getContent();// 锁定内容
        String orderNo = accountLockVo.getOrderNo();// 订单号
        String userAccountResultKey = "user:account:data" + orderNo;// redis的key

        // 2.检查账户余额是否充足
        // 如果充足：锁定，返回锁定成功的对象
        // 如果不足，锁定失败，返回失败的状态和提示消息
        // 传统方案：检查余额是否充足：
        //checkAmount();
        // 伪SQL: select *  from user_account where user_id= #{accountLockVo.getUserId()} and  available_amount>=#{accountLockVo.getAmount()}
        // 锁定余额
        //lockAmount();
        // 伪SQL update user_account set available_amount-#{accountLockVo.getAmount()},lock_account=lock_account+#{accountLockVo.getAmount()} where user_id=

        // CAS(比较并交换)利用CAS思想在MYSQL层面写锁实现互斥机制
        //  // 伪SQL update user_account set available_amount-#{accountLockVo.getAmount()},lock_account=lock_account+#{accountLockVo.getAmount()} where user_id=1 and available_amount>=#{accountLockVo.getAmount()}
        // 优化方案：利用CAS思想完成
        // 3.检查和锁定余额(setIfAbsent Lock)
        // 3.1 解决幂等性（OpenFeign的重试机制导致的重复发送请求）

        // 判断账户余额是否充足(锁定金额是否大于余额)
        if (amount.compareTo(getAvailableAmount(userId)) > 0) {
            // 余额不足锁定失败
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS);
        }
        List<String> range = redisTemplate.opsForList()
                .range("feign:retry:msg:container" + orderNo, -1, 0);
        assert range != null;
        if (range.contains(orderNo)) {
            // 幂等性,重复锁定
            return null;
        }
        try {
            // 将订单号 orderNo 推送到 Redis 列表的左侧
            Long aLong = redisTemplate.opsForList()
                    .leftPush("feign:retry:msg:container" + orderNo, orderNo);

            if (aLong != null && aLong == 1L) {
                // 执行数据库操作:CAS思想原子更改账户锁定余额
                userAccountMapper.opsDbCheckAndLockAccount(userId, amount);
                // 放值
                accountLockResultVo.setUserId(userId);
                accountLockResultVo.setAmount(amount);
                accountLockResultVo.setContent(content);
                // 将AccountLockResultVo对象存储到Redis(解锁和真正扣减余额操作方便)
                redisTemplate.opsForValue().set(userAccountResultKey, JSONObject.toJSONString(accountLockResultVo));
                // 保存账户的流水
                saveRunningWater(userId, "锁定:" + content, amount, orderNo);
                // 给上游
                return accountLockResultVo;
            } else {
                // 重复锁定
                return null;
            }
        } catch (Exception e) {
            // 只有抛出异常，才会执行删除操作
            redisTemplate.delete("feign:retry:msg:container" + orderNo);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR);
        }
    }

    /**
     * 获取账户可用余额
     * @param userId
     * @return
     */
    @Override
    public BigDecimal getAvailableAmount(Long userId) {
        UserAccount userAccount = userAccountMapper.selectByUserId(userId);
        if (userAccount != null) {
            return userAccount.getAvailableAmount();
        }else {
            return null;
        }

    }

    /**
     * 保存账户流水
     * @param userId
     * @param title
     * @param amount
     * @param orderNo
     */
    private void saveRunningWater(Long userId,
                                  String title,
                                  BigDecimal amount,
                                  String orderNo) {
        //1.初始化对象
        UserAccountDetail userAccountDetail = new UserAccountDetail();
        userAccountDetail.setUserId(userId);
        userAccountDetail.setTitle(title);
        userAccountDetail.setTradeType(SystemConstant.ACCOUNT_TRADE_TYPE_LOCK);
        userAccountDetail.setAmount(amount);
        userAccountDetail.setOrderNo(orderNo);
        userAccountDetailMapper.insert(userAccountDetail);
    }
}
